SvelteKit 提供了一系列 function 讓我們可以攔截特定事件並且可以對這些事件做出不同的回應,也就是類似 middleware 的概念。
主要分為 hooks.server.ts
、 hooks.client.ts
、 hooks.ts
看名字應該真的他們執行的地方在哪裡了,只是跟 page
或者 layout
不一樣的地方是 hooks
多了一個只有 hooks.client.ts
因為我個人覺得會運用到 hooks.server.ts
場景多了一點,所以接下來主要會聚焦在 hooks.server.ts
上。
當我們向 SvelteKit 的 server 發出任何請求時都會被 handle
function 所攔截到,不管是打+server.ts
的 API 還是 +(page|layout).server.ts
的 load
只要是跟 server 有關的請求都會被它攔截。
按照直覺我覺得應該是也要把
+page.ts
的 server-side 的執行也攔截住才對,但實際上並沒有
// in src/hooks.server.ts
import { type Handle } from '@sveltejs/kit';
export const handle: Handle = async ({ event, resolve }) => {
console.log(event.request.url);
return await resolve(event);
};
SvelteKit 預設是要把 kit 放在
src/
裡
然後新增 day23/+page.server.ts
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async () => {
console.log('day23/+page.server.ts load');
};
會發現我們只要有訪問/預載到 +page.server.ts
的 load
的路由或者有去執行 +server.ts
的 API 還有第一次進入網站/重新整理的那次 SSR 都會被 handle
所攔截到。
那它有什麼用?既然上述場景都能夠觸發 handle
function 那就很適合來當作登入驗證的 middleware 了吧,那接下來我們就來寫個「最最最最陽春」的登入驗證機制。
首先來實作登入的 API
// in day23/auth/+server.ts
import { error } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
export const POST: RequestHandler = async ({ request, cookies }) => {
const { email, password } = await request.json();
if (email === 'todd@example.com' && password === '123456') {
cookies.set('session', email, {
path: '/',
httpOnly: true,
maxAge: 60 * 60 * 24
});
return new Response(JSON.stringify({ success: true }));
}
error(401, 'Invalid email or password');
};
那因為 SvelteKit 的 API 路由本身就提供
cookies
這個類似 context 的寫法,所以就直接使用cookies.set
而不是放在 header 裡。
然後將 hooks.server.ts
改為
// in src/hooks.server.ts
import { error, type Handle } from '@sveltejs/kit';
export const handle: Handle = async ({ event, resolve }) => {
console.log('[hook]', event.request.url);
if (event.url.pathname === '/day23/detail') {
const session = event.cookies.get('session');
if (!session) {
error(401, 'Unauthorized');
}
}
return await resolve(event);
};
也就是說當我們要進入 /day23/detail
發現我們在 cookies
裡沒取得到 session
則回傳error(401, 'Unauthorized')
。
然後來接上登入 API
<!-- day23/+page.svelte -->
<script lang="ts">
import { goto } from '$app/navigation';
let email = $state('');
let password = $state('');
let response: Response | undefined = $state();
const login = async () => {
response = await fetch('/day23/auth', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ email, password })
});
if (response.ok) {
goto('/day23/detail');
}
};
</script>
<div class="flex flex-col mx-auto items-center">
<h1 class="text-primary">Login</h1>
<div class="w-1/2">
<label class="form-control w-full">
<div class="label">
<span class="label-text">Email</span>
</div>
<input name="email" type="text" class="input input-bordered w-full" bind:value={email} />
</label>
<label class="form-control w-full">
<div class="label">
<span class="label-text">Password</span>
</div>
<input
name="password"
type="password"
class="input input-bordered w-full"
bind:value={password}
/>
</label>
<div class="flex flex-col">
<button class=" btn btn-primary mt-12 w-1/2 mx-auto" type="button" onclick={login}
>Login</button
>
<button class="btn btn-secondary mt-4 w-1/2 mx-auto" type="button"> Register </button>
</div>
</div>
</div>
<div class="toast toast-center">
{#if response && !response.ok}
<div class="alert alert-error">
<span> login failed </span>
</div>
{/if}
</div>
這邊唯一比較需要說明的是有使用到 goto
這個 function ,它是 SvelteKit 裡進行 redirect 其中一個方法。
然後我們就能來實測我們的機制是否正常運作了
會發現我們如果在沒有成功登入的狀況下直接改網址到 /day23/detail
就會跳出錯誤,然後登入成功後就會自動到 /day23/detail
https://github.com/toddLiao469469/30days-for-svelte5/tree/main/src/routes/day23
https://github.com/toddLiao469469/30days-for-svelte5/blob/main/src/hooks.server.ts
https://30days-for-svelte5.pages.dev/